package com.googlecode.h0v3rk4rt.graphics;

import java.awt.AlphaComposite;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;
import javax.swing.JPanel;

import com.googlecode.h0v3rk4rt.net.MultiplayerClient;

//Moves the car and draws it
//Implementation notes:
//- Time in milliseconds, distances in pixels, angles in radians
//- v += a * dt, x += v * dt)...
//- ...in addition to v *= (velocity multiplier)^(time elapsed) 

public class DrivePanel extends JPanel
{
	private static final long serialVersionUID = 1L; //default UID

	private static final int N = 2;

	private static final double leftA = -.001; //angular velocity from left
	private static final double rightA = .001; //angular velocity from right
	private static final double upA = .0002; //acceleration from up
	private static final double downA = -.0001; //acceleration from down
	private static final double brakeA = .995; //velocity multiplier from brake

	private static final double colorMultiplier = .998; //multiplied if red, divided if green
	private static final double blackMultiplier = .99;
	private static final double normalMultiplier = .999; //this always gets multiplied

	//local physics variables
	private double[] x, x0, xOther;
	private double theta, omega, alpha;
	private double v, a, multiplier;
	private int lastColor = 0;
	public static int laps;

	//server forced variables
	public static double[] xThem;
	public static double vThem;
	public static double thetaThem;
	public static long timeThem;
	private long lastTimeThem;
	public static double[] xMe = new double[2];
	public static double vMe;
	public static double thetaMe;
	public static long timeMe;
	private long lastTimeMe;

	private int[] w;
	private long lastTime; //last time it has been drawn

	private BufferedImage map;
	private BufferedImage background;

	private BufferedImage image;

	public DrivePanel(int w, int h) throws IOException
	{
		laps = 0;
		lastTime = System.currentTimeMillis();
		x = new double[] {
				w / 2 + -50 + 50 * Math.random(),
				h / 2 + -50 + 50 * Math.random() };
		xOther = new double[] {
				-50, -50 };
		xThem = new double[] {
				-50, -50 };
		x0 = new double[2];
		v = 0;
		a = 0;
		multiplier = normalMultiplier;
		this.w = new int[] {
				w, h };
		this.setPreferredSize(new Dimension(w, h));
		this.addKeyListener(new DriveListener());
		map = ImageIO.read(new File("resource/map.png"));
		image = new BufferedImage(map.getWidth(), map.getHeight(), BufferedImage.TYPE_INT_ARGB);
		//			background = ImageIO.read(new File("wQ.png"));
		background = new BufferedImage(50, 50, BufferedImage.TYPE_BYTE_BINARY);
		background.createGraphics().drawRect(0, 0, 49, 49);
	}

	public void step()
	{
		long time = System.currentTimeMillis();
		MultiplayerClient.sendMe(x[0], x[1], v, theta, time); //send my data
		double dt = (double) (time - lastTime);
		lastTime = time;

		//Move car:
		if (timeMe > lastTimeMe)
		{ //check if we've collided
			x[0] = xMe[0];
			x[1] = xMe[1];
			v = vMe;
			theta = thetaMe;
			lastTimeMe = timeMe;
		}
		omega += alpha * dt;
		theta += omega * dt;
		v += a * dt;
		double x0old = x[0], x1old = x[1];
		x[0] += v * Math.cos(theta) * dt;
		x[1] += v * Math.sin(theta) * dt;
		v *= Math.pow(multiplier, dt);

		//Assimilate UDP data
		if (timeThem > lastTimeThem)
		{
			xOther[0] = xThem[0];
			xOther[1] = xThem[1];
			lastTimeThem = System.currentTimeMillis();
		}

		//Adjust for color of background:
		int pixel = 0; // default is a wall
		double red = 0, blue = 0, green = 0;
		if (x[0] >= 0 && x[0] < map.getWidth() && x[1] >= 0 && x[1] < map.getHeight())
		{
			pixel = map.getRGB((int) x[0], (int) x[1]) & 0x00FFFFFF;
			red = (pixel & 0x00FF0000) >>> 16;
			green = (pixel & 0x0000FF00) >>> 8;
			blue = (pixel & 0x000000FF) >>> 0;
		}
		double red2 = red / (red + blue + green + 1);
		double green2 = green / (red + blue + green + 1);
		//double blue2 = blue / (red + blue + green + 1);

		if (pixel == 0)
		{
			x[0] = x0old;
			x[1] = x1old;
			v *= Math.pow(blackMultiplier, dt);
		}
		else if (pixel == 0x007FFF)
		{
			if (lastColor == 0x00FFFF) laps++;
		}
		else if (pixel == 0x00FFFF)
		{
			if (lastColor == 0x007FFF) laps--;
		}
		else
		{
			v /= Math.pow(Math.pow(colorMultiplier, green2), dt);
			v *= Math.pow(Math.pow(colorMultiplier, red2), dt);
		}
		for (int i = 0; i < N; i++)
			x0[i] = -x[i] + w[i] / 2;
		lastColor = pixel;
	}
	protected void paintComponent(Graphics g1)
	{
		step();
		Graphics2D g2 = (Graphics2D) g1;
		g2.setBackground(Color.black);
		g2.clearRect(-1, -1, image.getWidth() + 2, image.getHeight() + 2);
		w[0] = this.getWidth();
		w[1] = this.getHeight();
		Graphics2D g = image.createGraphics();
		g.setBackground(new Color(0, 0, 0, 0));
		g.clearRect(-1, -1, image.getWidth() + 2, image.getHeight() + 2);
		g.setColor(Color.blue);
		g.fillOval((int) x[0] - 8, (int) x[1] - 8, 15, 15);
		g.setColor(Color.pink);
		g.fillOval((int) xOther[0] - 8, (int) xOther[1] - 8, 15, 15); //other guy
		g.setColor(Color.black);
		g.drawLine((int) x[0], (int) x[1], (int) (x[0] + 8 * Math.cos(theta)), (int) (x[1] + 8 * Math.sin(theta)));

		//		g2.rotate(-theta - Math.PI / 2, x[0] + x0[0], x[1] + x0[1]);

		AffineTransform xform = new AffineTransform();
		xform.concatenate(AffineTransform.getTranslateInstance(x0[0], x0[1]));
		xform.concatenate(AffineTransform.getRotateInstance(-theta - Math.PI / 2, x[0], x[1]));
		g2.drawImage(map, xform, this);
		g2.drawImage(image, xform, this);

		//		g2.drawImage(map, 0, 0, w[0], w[1], (int) -x0[0], (int) -x0[1], (int) -x0[0] + w[0], (int) -x0[1] + w[1], this);
		//		g2.drawImage(image, 0, 0, w[0], w[1], (int) -x0[0], (int) -x0[1], (int) -x0[0] + w[0], (int) -x0[1] + w[1], this);
		g.transform(AffineTransform.getRotateInstance(+theta + 3 * Math.PI / 2, x[0], x[1]));
		g.setColor(new Color(255, 255, 255, 100));
		g.fillRect((int) -x0[0], (int) -x0[1], w[0], w[1]);
		drawOnMinimap(g, x0[0], x0[1], Color.blue);
		//drawOnMinimap(g, xOther[0] + x[0] + x0[0], -xOther[1] + x[1] + x0[1], Color.pink);
		g.setColor(Color.blue);
		g.drawRect((int) -x0[0], (int) -x0[1], w[0], w[1]);
		//		g2.rotate(theta + Math.PI / 2, x[0] + x0[0], x[1] + x0[1]);
		g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, .65f));
		if (image.getHeight() < image.getWidth())
		{
			g2.drawImage(map, 0, 0, 300, 300 * image.getHeight() / image.getWidth(), 0, 0, image.getWidth(), image.getHeight(), this);
			g2.drawImage(image, 0, 0, 300, 300 * image.getHeight() / image.getWidth(), 0, 0, image.getWidth(), image.getHeight(), this);
		}
		else
		{
			g2.drawImage(map, 0, 0, 300 * image.getWidth() / image.getHeight(), 300, 0, 0, image.getWidth(), image.getHeight(), this);
			g2.drawImage(image, 0, 0, 300 * image.getWidth() / image.getHeight(), 300, 0, 0, image.getWidth(), image.getHeight(), this);
		}
		g.dispose();
		g1.dispose();
	}

	private void drawOnMinimap(Graphics2D g, double xpos, double ypos, Color c)
	{
		g.setColor(c);
		g.fillOval((int) -xpos + w[0] / 2 - 23, (int) -ypos + w[1] / 2 - 23, 45, 45);
		g.setStroke(new BasicStroke(10.0f, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER));
		g.drawLine((int) -xpos + w[0] / 2, (int) -ypos + w[1] / 2, (int) -xpos + w[0] / 2, (int) -ypos + 3 * w[1] / 4);
		g.drawPolyline(new int[] {
				(int) -xpos + w[0] / 2 - w[0] / 25, (int) -xpos + w[0] / 2,
				(int) -xpos + w[0] / 2 + w[0] / 25 }, new int[] {
				(int) -ypos + 3 * w[1] / 4 - w[1] / 25,
				(int) -ypos + 3 * w[1] / 4,
				(int) -ypos + 3 * w[1] / 4 - w[1] / 25 }, 3);
	}

	private class DriveListener extends KeyAdapter
	{
		public void keyPressed(KeyEvent e)
		{
			setAccel(e, 1);
		}

		public void keyReleased(KeyEvent e)
		{
			setAccel(e, 0);
		}

		private void setAccel(KeyEvent e, int m)
		{
			int key = e.getKeyCode();
			switch (key)
			{
				case KeyEvent.VK_A:
					omega = leftA * m;
					break;
				case KeyEvent.VK_D:
					omega = rightA * m;
					break;
				case KeyEvent.VK_W:
					a = upA * m;
					break;
				case KeyEvent.VK_S:
					a = downA * m;
					break;
				case KeyEvent.VK_SPACE:
					multiplier = brakeA * m;
					if (m == 0) multiplier = normalMultiplier;
			}
		}
	}
	
	public void shutdown() {
		lastTimeThem = 0;
		lastTimeMe = 0;
		laps = 0;
		map = null;
		background = null;
		image = null;
	}
}